class BoidList
{
  private ArrayList<Boid> m_boids;
  private WorldState m_worldState;
  
  public BoidList()
  {
    m_boids = new ArrayList<Boid>();
    m_worldState = new WorldState();
    for (int i = 0; i < NUMBOIDS; i++)
    {
      m_boids.add(new Boid(i));
    }
  }
  
  private boolean addToMap(Map<Boid, QueryResult> map, PVector position, Boid b, float radius, FrameType type)
  {
    Float distance = b.position().dist(position);
    if (distance < radius)
    {
      PVector directionFrom = new PVector(b.position().x - position.x, b.position().y - position.y);
      map.put(b, new QueryResult(distance, directionFrom, type));
      return true;
    }
    return false;
  }
  
  public Map<Boid, QueryResult> getNeighbors(float radius, Boid querierBoid)
  {
    Map<Boid, QueryResult> retList = new HashMap<Boid, QueryResult>();
    
    //Handling torroidal wrapping by adding radius sized extended windows to the von-neumann neighborhood
    //TODO msati3: Currently, this torroidal wrapping is not hadling the moore's neighborhood. Factor that in??
    
    //TODO msati3: PVector doesn't have a distance square function. Use your own for speed?
    PVector lFramePos = null;
    PVector rFramePos = null;
    PVector uFramePos = null;
    PVector dFramePos = null;
        
    if (querierBoid.position().x + radius > WIDTH)
    {
      rFramePos = new PVector(querierBoid.position().x - WIDTH, querierBoid.position().y);
    }
    if (querierBoid.position().y + radius > HEIGHT)
    {
      dFramePos = new PVector(querierBoid.position().x, querierBoid.position().y - HEIGHT);
    }
    if (querierBoid.position().x - radius < 0)
    {
      lFramePos = new PVector(WIDTH + querierBoid.position().x, querierBoid.position().y);
    }
    if (querierBoid.position().y - radius < 0)
    {
      uFramePos = new PVector(querierBoid.position().x, HEIGHT + querierBoid.position().y);
    }
    for (Boid b : m_boids)
    {
      if (b.id() != querierBoid.id())
      {
        boolean addedToMap = false;
        addedToMap = addToMap(retList, querierBoid.position(), b, radius, FrameType.Same);
        if (!addedToMap && (lFramePos != null))
        {
          addedToMap = addToMap(retList, lFramePos, b, radius, FrameType.Left);
        }
        if (!addedToMap && (rFramePos != null))
        {
          addedToMap = addToMap(retList, rFramePos, b, radius, FrameType.Right);
        }
        if (!addedToMap && (uFramePos != null))
        {
          addedToMap = addToMap(retList, uFramePos, b, radius, FrameType.Top);
        }
        if (!addedToMap && (dFramePos != null))
        {
          addedToMap = addToMap(retList, dFramePos, b, radius, FrameType.Bottom);
        }
      }
    }
    return retList;
  }
  
  public void update()
  {
    if (!m_worldState.fPaused())
    {
      for (Boid b : m_boids)
      {
        b.update(this);
      }
      for (Boid b : m_boids)
      {
        b.applyUpdate();
      }
    }
  }
  
  public void draw()
  {
    if (m_worldState.fLeaveTrail())
    {
      for (Boid b : m_boids)
      {
        b.drawTrail();
      }
    }
    for (Boid b : m_boids)
    {
      b.draw();
    }
    text("Centering: " + m_worldState.fCentering() + "   Velocity Matching: " + m_worldState.fVelocityMatch() + "   Collisions: " + m_worldState.fCollision() + "  Wandering: " + m_worldState.fRandom(), 100, 50);
  }
  
  public void reset()
  {
    for (Boid b : m_boids)
    {
      b.reinit();
    }
  }
  
  public void addBoid()
  {
    int numBoids = m_boids.size();
    if (numBoids < MAX_BOIDS)
    {
      m_boids.add(new Boid(numBoids));
    }
  }
  
  public void removeBoid()
  {
    int numBoids = m_boids.size();
    if (numBoids > MIN_BOIDS)
    {
      int removedBoid = (int)random(numBoids);
      m_boids.remove(removedBoid);
    }
  }
  
  public void toggleCentering()
  {
    m_worldState.toggleCenter();
  }
  
  public void toggleVelocityMatch()
  {
    m_worldState.toggleVelocityMatch();
  }
  
  public void toggleCollision()
  {
    m_worldState.toggleCollision();
  }
  
  public void toggleRandom()
  {
    m_worldState.toggleRandom();
  }
  
  public void toggleAttract()
  {
    m_worldState.toggleAttract();
  }
  
  public void toggleRepel()
  {
    m_worldState.toggleRepel();
  }
  
  public void togglePause()
  {
    m_worldState.togglePause();
  }
  
  public void toggleLeaveTrail()
  {
    m_worldState.toggleLeaveTrail();
  }
  
  public void clear()
  {
    for (Boid b : m_boids)
    {
      b.clearPath();
    }
  }
}

